Technical Note TN2107
Working Around Incorrect -needsToDrawRect: Behavior in Custom View Classes

CONTENTS

NSView's -needsToDrawRect: convenience method, which first appeared in Panther, incorrectly returns NO in some circumstances. This may result in incomplete drawing in a custom view class that relies on -needsToDrawRect: to determine which parts of its content it needs to draw.

This TechNote explains how to work around the incorrect Panther behavior, which will be fixed in the next major release of Mac OS X.

[Feb 06, 2004]



Background

Mac OS X 10.3 introduced a more optimized drawing model for Cocoa views. Supported by new NSView methods -getRectsBeingDrawn:count: and -needsToDrawRect: , this model enables a view to more precisely constrain its drawing to only those parts of its content area that need to be updated. Use of these new methods is discussed in detail in Drawing and Views subtopic Constraining Drawing to Improve Performance .

The -needsToDrawRect: method is provided as a convenient means for a view's -drawRect: implementation to check whether some particular object in its content area needs to be drawn. Given the object's bounding rectangle, -needsToDrawRect: should return YES if this rectangle intersects any part of the view that needs redisplay.

Back to top



The Problem

As an efficiency measure, NSView's -needsToDrawRect: implementation first tests whether the rectangle it is given intersects the bounding box of the area being drawn. In some cases -- most often when text is being drawn in the view -- this bounding rectangle can be computed incorrectly, which can cause -needsToDrawRect: to incorrectly return NO, and drawing that is conditioned on this result to be incorrectly suppressed.

Back to top



Overriding -needsToDrawRect: to Perform Correctly

You can work around this problem by overriding -needsToDrawRect: to perform as intended, in any custom view class(es) in which you use -needsToDrawRect:.

If a view is rarely asked to draw more than a few rectangles at a time, and its content is not particularly complex or expensive to draw, the following reimplementation of -needsToDrawRect: will suffice.



Listing 1. A Simple -needsToDrawRect: Replacement

- (BOOL)needsToDrawRect:(NSRect)rect
{
    const NSRect *rectList;
    int count;
    int i;
    
    [self getRectsBeingDrawn:&rectList count:&count];    
    for (i = 0; i < count; i++) {
        if (NSIntersectsRect(rect, rectList[i])) {
            return YES;
        }
    }
    return NO;
}



The above approach has the advantage of not requiring any changes to code that calls -needsToDrawRect:.

To improve performance in cases where a view is typically given many rectangles to draw at once, you may choose to define and use an alternative to this method that quickly rejects objects that lie outside the rectList's bounding rect. You would invoke this method instead of -needsToDrawRect:, passing it -drawRect:'s rectangle parameter as its "rectListBounds" parameter.



Listing 2. A More Optimized -needsToDrawRect: Replacement

- (BOOL)needsToDrawRect:(NSRect)rect rectListBounds:(NSRect)rectListBounds
{
    const NSRect *rectList;
    int count;
    int i;
    
    if (!NSIntersectsRect(rect, rectListBounds)) {
        return NO;
    }
    [self getRectsBeingDrawn:&rectList count:&count];
    if (count == 1) {
        return YES;
    } else {
        for (i = 0; i < count; i++) {
            if (NSIntersectsRect(rect, rectList[i])) {
                return YES;
            }
        }
        return NO;
    }
}




Developer Documentation | Technical Q&As | Development Kits | Sample Code